{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ">原文地址 [mp.weixin.qq.com](https://mp.weixin.qq.com/s/2-obGqrWCBfFV2-Lw8gCRg)\n",
    "\n",
    "很多开发者用自己的电脑开发，然后将程序部署在内网。如果内网无法访问互联网，部署就相当麻烦，你需要将应用程序依赖的包也传输到内网。如果是 Python 应用，还需要使用 pip 安装一下依赖包，对于某些需要编译安装包，windows 环境下安装过程中还可能报错，linux 可能提示缺失必要的头文件等，安装这些依赖包会耗费较多的时间，对自己的技术提升其实没有帮助，是要尽可能避免的。\n",
    "\n",
    "那么能不能一次部署，处处运行？其实这个问题已经有点过时了，别问，问就是 docker，如果你了解过 docker，你就知道，以容器的形式来发布你的应用程序，已经变得非常流行。\n",
    "\n",
    "本文教你如何将一个 Python 应用容器化，容器化后，你的应用可以极其方便的迁移至其他具有 Docker 的服务器中，即使 Docker 服务器不联网，也只需将一个 Docker 镜像拷贝至内网即可完成部署，实现一次部署，处处运行，提升部署应用的效率。\n",
    "\n",
    "假如有一个 Python 的 web 应用 index.py ，这里是 falsk 写的 Hello world：\n",
    "\n",
    "```py\n",
    "from flask import Flask\n",
    "app = Flask(__name__)\n",
    "@app.route(\"/\")\n",
    "def hello():\n",
    "    return \"Hello World!\"\n",
    "if __name__ == \"__main__\":\n",
    "    app.run(host=\"0.0.0.0\", port=int(\"5000\"), debug=True)\n",
    "```\n",
    "\n",
    "运行 `python index.py` 后，在浏览器上访问 `http://127.0.0.1:5000`，会看到浏览器上显示了'Hello World!' 说明程序没有问题。如果我们把这个文件发给别人，那么他需要做以下 3 步才能启动这个 web 应用。\n",
    "\n",
    "1.  安装 Python 解释器\n",
    "    \n",
    "2.  安装 flask 库\n",
    "    \n",
    "3.  执行 `python index.py`\n",
    "    \n",
    "\n",
    "真实情况下，步骤 2 可能会有很多依赖，这里我们将所有的依赖库都放在 `requirements.txt` 文件中：\n",
    "\n",
    "```ini\n",
    "Flask==1.1.2\n",
    "```\n",
    "\n",
    "然后在应用程序的根目录（这里是 index.py 所在的目录）中创建一个名为 Dockerfile 的文本文件，然后粘贴以下代码。\n",
    "\n",
    "```docker\n",
    "FROM python:alpine3.8\n",
    "COPY . /app\n",
    "WORKDIR /app\n",
    "RUN pip install -r requirements.txt\n",
    "EXPOSE 5000\n",
    "CMD python ./index.py\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "请注意，FROM 指令指向 `python:alpine3.7`。这是在告诉 Docker 容器要使用什么基本映像，并隐式选择要使用的 Python 版本，在本例中为 3.8。Docker Hub 具有几乎所有 Python 版本的基本映像。该示例使用的是安装在 Alpine Linux（极简 Linux 发行版）上的 Python，该 Python 发行版有助于使 Docker 的映像较小。除非有充分的理由使用 Debian Jessie 这样的基本图像，否则最好选择 Alpine。\n",
    "\n",
    "还要注意的是 RUN 指令，该指令调用 pip 从 requirements.txt 文件安装依赖项。\n",
    "\n",
    "Dockerfile 中的其余指令非常简单。在 CMD 指令告诉容器来执行启动该应用程序是什么。在这种情况下，它告诉 Python 运行 index.py。该 COPY 指令简单地移动应用到容器镜像，WORKDIR 设置工作目录，EXPOSE 绑定 Flask 使用的端口。\n",
    "\n",
    "要构建映像，在 Dockerfile 目录下，终端运行 `docker build` 命令。\n",
    "\n",
    "```sh\n",
    "docker build --tag somenzz-app ./\n",
    "```\n",
    "\n",
    "等待其下载基本镜像并构建完成，会得到一个 somenzz-app 的镜像:\n",
    "\n",
    "```sh\n",
    "$ docker images | grep somenzz\n",
    "somenzz-app                                         latest              b7e2359056bb        3 hours ago         89.1MB\n",
    "somenzz/my-kali                                     latest              975cbe3a4619        2 weeks ago         1.83GB\n",
    "```\n",
    "\n",
    "可以看到此镜像只有 89.1MB 的大小。接下来就像其他镜像已有可以使用了：\n",
    "\n",
    "```sh\n",
    "$ docker run --name python-app -p 5000:5000 somenzz-app\n",
    " \\* Serving Flask app \"index\" (lazy loading)\n",
    " \\* Environment: production\n",
    "   WARNING: This is a development server. Do not use it in a production deployment.\n",
    "   Use a production WSGI server instead.\n",
    " \\* Debug mode: on\n",
    " \\* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)\n",
    " \\* Restarting with stat\n",
    " \\* Debugger is active!\n",
    " \\* Debugger PIN: 201-219-912\n",
    "172.17.0.1 - - \\[14/Oct/2020 22:32:05\\] \"GET / HTTP/1.1\" 200 -\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "此时容器已经成功启动，本机的 5000 端口会映射至容器的 5000 端口，因此，访问 `127.0.0.0:5000` 就相当于访问容器的 5000 端口。\n",
    "\n",
    "如果要后台运行容器，可以加入 `-d` 参数：\n",
    "\n",
    "```sh\n",
    "$ docker run -d  --name python-app -p 5000:5000 somenzz-app\n",
    "259d09fbbf77a6fd680cd7527600d055cb76fe3d6792063846103b47360210f9\n",
    "$ docker container list | grep somenzz-app\n",
    "259d09fbbf77        somenzz-app                                         \"/bin/sh -c 'python …\"   20 minutes ago      Up 20 minutes       0.0.0.0:5000->5000/tcp                           python-app\n",
    "\n",
    "\n",
    "```\n",
    "\n",
    "结合前文[手摸手，带你入门 docker](http://mp.weixin.qq.com/s?__biz=MzU0OTg3NzU2NA==&mid=2247484908&idx=1&sn=64c49a211286ba9f1113ee0a75b5aef0&chksm=fba867bfccdfeea95fd2dca7a6de43cca02db4413347f3a5ef46652e20b68fdd4a0ca4f4ca60&scene=21#wechat_redirect) 中的步骤 7 ：发布镜像，你可以将容器发布到网络上供他人使用。\n",
    "\n",
    "如果你不熟悉 Dockerfile，制作并发布 docker 镜像还有一个更简单的方法，那就是下载一个镜像，启动，登陆到容器内部进行修改，安装依赖，然后保存为新镜像，最后发布，当然效率是比较低的，有时间最好还是学习下 Dockerfile， 官方教程如下：\n",
    "\n",
    "https://docs.docker.com/engine/reference/builder/\n",
    "\n",
    "自然，本文只是一个简单的 HelloWorld web 应用。更复杂的场景将需要更多地关注细节，但是大多数 Python 应用程序的容器化流程都是相同的，希望本文对你有所帮助。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.7"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
